﻿/// Copyright © 2013 Microsoft Corporation. 
/// This project is released under the Microsoft Public License (MS-PL)
///
namespace DataMorph.Generator
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    using DataMorph.Common;
    using DataMorph.Common.Contracts;

    public class TagDataSetGenerator : ITagDataSetGenerator
    {
        private static readonly ThreadLocal<Random> Random = new ThreadLocal<Random>(() => new Random());

        private readonly object lockObject = new object();

        private readonly ITagDataFileGenerator tagDataFileGenerator;

        private readonly ITagGenerator tagGenerator;

        private readonly Func<string, int, ITagDataFileWriter> writerFactory;

        private bool cancelled;

        public TagDataSetGenerator(
                            ITagDataFileGenerator tagDataFileGenerator, 
                            ITagGenerator tagGenerator,
                            Func<string, int, ITagDataFileWriter> writerFactory)
        {
            this.tagDataFileGenerator = tagDataFileGenerator;
            this.tagGenerator = tagGenerator;
            this.writerFactory = writerFactory;
        }

        public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

        public TagDataSet Generate(GenerationParameters parameters)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters", "Parameters must not be null.");
            }

            var daysOfData = DistributeFilesAccrossIntervals(
                                                    parameters.NumberOfFiles,
                                                    new DateTime(parameters.StartDateTime.Year, parameters.StartDateTime.Month, parameters.StartDateTime.Day),
                                                    TimeSpan.TicksPerDay,
                                                    parameters.NumberOfDays).ToList();

            var tags = this.tagGenerator.Generate(parameters.NumberOfTags);
            var dataSet = new TagDataSet(parameters.Name, tags, parameters.StartDateTime, parameters.StartDateTime + TimeSpan.FromDays(parameters.NumberOfDays));

            var generatedFileCount = 0;
            Parallel.ForEach(
                        daysOfData, 
                        dayOfData =>
                        {
                            var paths = this.GenerateAndWriteFilesForDay(
                                                                    dayOfData,
                                                                    tags.ToList(),
                                                                    parameters.MinFileSize,
                                                                    parameters.MaxFileSize,
                                                                    writerFactory(dataSet.Name, dayOfData.Number),
                                                                    () => UpdateProgress(++generatedFileCount, parameters.NumberOfFiles));

                            dataSet.AddFiles(dayOfData.Number, paths);
                        });

            return dataSet;
        }

        public void Cancel()
        {
            this.cancelled = true;
        }

        private static IEnumerable<IntervalGenerationParameters> DistributeFilesAccrossIntervals(
            int fileCount,
            DateTime startDate,
            long intervalTicks,
            int intervalCount)
        {
            return EnumerableExtensions.Batch(
                                            fileCount,
                                            intervalCount,
                                            (batchIndex, batchSize) => new IntervalGenerationParameters
                                            {
                                                Number = batchIndex + 1,
                                                FileCount = batchSize,
                                                FromDate = startDate + TimeSpan.FromTicks(intervalTicks * batchIndex),
                                                ToDate = startDate + TimeSpan.FromTicks(intervalTicks * (batchIndex + 1)),
                                            }).ToList();
        }

        private List<string> GenerateAndWriteFilesForDay(
                                                IntervalGenerationParameters parameters, 
                                                IEnumerable<Tag> tags, 
                                                int minFileSize, 
                                                int maxFileSize, 
                                                ITagDataFileWriter writer,
                                                Action progressCallback)
        {
            var paths = new List<string>();

            for (int j = 0; j < parameters.FileCount; j++)
            {
                if (this.cancelled)
                {
                    break;
                }

                var fileSize = Random.Value.Next(minFileSize, maxFileSize);
                var file = this.tagDataFileGenerator.Generate(
                                                            tags,
                                                            fileSize,
                                                            parameters.FromDate,
                                                            parameters.ToDate);

                paths.Add(writer.Write(file));

                lock (this.lockObject)
                {
                    progressCallback();
                }
            }

            return paths;
        }

        private void UpdateProgress(int generatedFileCount, int fileCount)
        {
                this.RaiseProgressChangedEvent(
                                    Convert.ToInt32((generatedFileCount / (double)fileCount) * 100),
                                    string.Format("{0} of {1} files generated", generatedFileCount, fileCount));
        }

        private void RaiseProgressChangedEvent(int progress, string message)
        {
            if (this.ProgressChanged != null)
            {
                this.ProgressChanged(this, new ProgressChangedEventArgs(progress, message));
            }
        }

        private class IntervalGenerationParameters
        {
            public int Number { get; set; }

            public int FileCount { get; set; }

            public DateTime FromDate { get; set; }

            public DateTime ToDate { get; set; }
        }
    }
}